package se.sics.tac.aw;

import static java.lang.Math.round;
import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class FlightAuctionTest {
	FlightAuction fa;
	int auctionID = 0;

	@Before
	public void setUp() throws Exception {
		fa = new FlightAuction(auctionID);
	}

	@Test
	public void testGetAuctionID() {
		assertEquals(auctionID,fa.getAuctionID());
	}

	@Test
	public void testSeedProbability() throws Exception {
		fa.updatePrice(0, 250);
		assertEquals("sumSeedProbability", 1.0, fa.sumSeedProbability(-10, 30), 0.00001);
		assertEquals("seedIncreasing", 0.4878, fa.seedIncreasing(), 0.00001);
		assertEquals("seedDecreasing", 0.5122, fa.seedDecreasing(), 0.00001);
		
		double[] flightDistribution = fa.getSeedDistribution();
		double expected = 1.0 / 41.0;
		for (int i=0; i<41; i++)
			assertEquals("seedDistribution["+i+"]", flightDistribution[i], expected, 0.00001);
	}

	@Test
	public void testMinuteProbability() throws Exception {
		fa.updatePrice(0, 250);
		assertEquals("minuteProbability", 1.0, fa.minuteProbability(150, 800), 0.00001);
		assertEquals("minuteBetter", 0.46277, fa.minuteBetter(), 0.00001);
		assertEquals("minuteWorse", 0.48532, fa.minuteWorse(), 0.00001);
		
		double[] testDistribution = calcFullPriceDistribution(250,0,60);
		double[] flightDistribution = fa.getMinuteDistribution();
		for (int i=0; i<651; i++)
			assertEquals("minuteDistribution["+i+"]", testDistribution[i], flightDistribution[i],
				0.00001);
	}
	
	@Test
	public void testPriceProbability() throws Exception {
		fa.updatePrice(0, 250);
		fa.updatePriceDistribution();
		assertEquals("priceProbability", 1.0, fa.priceProbability(150, 800), 0.00001);
		assertEquals("priceBetter", 0.65611, fa.priceBetter(), 0.00001);
		assertEquals("priceWorse", 0.31949, fa.priceWorse(), 0.00001);
	}
	
	@Test
	public void testUpdatePrice() throws Exception {
		try{
			fa.updatePrice(-10, 250);
			fail("Exception expected for low time");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(-550, 250);
			fail("Exception expected for high time");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(0, 249);
			fail("Exception expected for low initial price");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(0, 401);
			fail("Exception expected for high initial price");
		} catch (Exception e) {}
		
		fa.updatePrice(0, 250);
		
		try{
			fa.updatePrice(0, 250);
			fail("Exception expected for low time");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(10, 149);
			fail("Exception expected for low price");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(10, 801);
			fail("Exception expected for high price");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(10, 239);
			fail("Exception expected for low price change");
		} catch (Exception e) {}
		
		try{
			fa.updatePrice(10, 261);
			fail("Exception expected for high price change");
		} catch (Exception e) {}
		
		fa.updatePrice(10, 250);
		fa.updatePrice(30, 270);
	}
	
	@Test
	public void testConstantGame() throws Exception {
		for (int time=0; time<540; time+=10)
			fa.updatePrice(time, 400);
		assertEquals(true,fa.buyNow(530000));
	}
	
	@Test
	public void testSkippingGame() throws Exception {
		for (int time=10; time<540; time+=40)
			fa.updatePrice(time, 400);
		assertEquals(true,fa.buyNow(530000));
	}
	
	@Test
	public void testPriceChange() throws Exception {
		fa.updatePrice(520, 400);
		fa.updatePrice(530, 430);
	}
	
	public static double [] calcPriceDistribution(int startPrice, int seed, int startTime, int endTime) {
		double distribution [] = new double[651];
		
		int minPriceIndex = startPrice - 150;
		int maxPriceIndex = startPrice - 150;
		distribution [minPriceIndex] = 1.0;
		
		for (int time = startTime+10; time <= endTime; time+=10)
		{
			double xt = 10.0 + time / 540.0 * (seed - 10.0);	// x(t) := 10 + time/540 * (seed - 10)
			int xt2 = (int) round(xt);
			if (xt2<0)	xt2++;
			int minPriceChange = (xt >= 0.0) ? -10 : xt2;	// minimum bound for price change
			int maxPriceChange = (xt < 0.0) ?  10 : xt2;	// maximum bound for price change
			double prob = 1.0 / (maxPriceChange - minPriceChange + 1);
			
			double newDistribution [] = new double[651];

			for (int priceIndex = minPriceIndex; priceIndex <= maxPriceIndex; priceIndex++)
				for (int i = minPriceChange; i <= maxPriceChange; i++)
				{
					int newPriceIndex = priceIndex + i;
					if (newPriceIndex<0)		newPriceIndex = 0;
					else if (newPriceIndex>650)	newPriceIndex = 650;
					
					newDistribution[newPriceIndex] += distribution[priceIndex] * prob;
				}

			minPriceIndex += minPriceChange;
			if (minPriceIndex < 0) minPriceIndex = 0;
			
			maxPriceIndex += maxPriceChange;
			if (maxPriceIndex > 650) maxPriceIndex = 650;
			
			distribution = newDistribution;
		}
		
		return distribution;
	}
	
	public double[] calcFullPriceDistribution(int startPrice, int startTime, int endTime){
		double[] distribution = new double[651];
		double prob = 1.0/41.0;
		
		for (int seed=-10; seed<=30; seed++)
		{
			double[] tempDistribution =
				calcPriceDistribution(startPrice, seed, startTime, endTime);

			for (int i=0; i<651; i++)
				distribution[i] += tempDistribution[i] * prob;
		}		
		
		return distribution;
	}
}